/**
 * Title: LoginServiceImpl.java
 * Description: Copyright: Copyright (c) 2019 Company: BHFAE
 *
 * @author Sharp
 * @date 2019-7-26 17:56
 * @description Project Name: Grote
 * @Package: com.sharp.authorization.service.impl
 */
package com.sharp.authorization.service.login.impl;

import com.sharp.authorization.bo.user.UserInfo;
import com.sharp.authorization.config.context.AccountContext;
import com.sharp.authorization.constants.common.ClientTypeConst;
import com.sharp.authorization.constants.err.AccountErrCode;
import com.sharp.authorization.constants.param.AccountParam;
import com.sharp.authorization.constants.token.TokenItemConst;
import com.sharp.authorization.dao.common.entity.AcRole;
import com.sharp.authorization.dao.common.entity.AcUser;
import com.sharp.authorization.dao.common.repository.AcUserRoleService;
import com.sharp.authorization.dao.common.repository.AcUserService;
import com.sharp.authorization.dao.join.entity.UserWithRoleList;
import com.sharp.authorization.dao.join.repository.UserJoinRoleService;
import com.sharp.authorization.holder.ClientTypeHolder;
import com.sharp.authorization.service.docking.utils.DockingSignUtil;
import com.sharp.authorization.service.login.ChangePasswordService;
import com.sharp.authorization.service.login.LoginService;
import com.sharp.authorization.service.permit.PermitCoreService;
import com.sharp.authorization.service.token.TokenService;
import com.sharp.authorization.vo.login.LoginRes;
import com.sharp.authorization.vo.login.QrCodeReq;
import com.sharp.authorization.vo.login.SsoLoginReq;
import com.sharp.authorization.vo.permit.PermitRes;
import com.sharp.authorization.vo.platform.OpenPlatformAccessTokenResp;
import com.sharp.authorization.vo.platform.OpenPlatformRegResp;
import com.sharp.kernel.constant.CommonConst;
import com.sharp.kernel.constant.db.SqlConstant;
import com.sharp.kernel.constant.err.ErrCodeSys;
import com.sharp.framework.service.cache.FrameCacheService;
import com.sharp.kernel.utils.ReflectionUtil;
import com.sharp.framework.utils.security.Base64Util;
import com.sharp.framework.utils.security.MD5Util;
import com.sharp.framework.utils.security.RandomUtil;
import com.sharp.kernel.validate.Validator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;

@Service
public class LoginServiceImpl implements LoginService {

    @Resource
    private AcUserService acUserService;
    @Resource
    private FrameCacheService frameCacheService;
    @Resource
    private TokenService tokenService;
    @Resource
    private PermitCoreService permitCoreService;
    @Resource
    private AcUserRoleService acUserRoleService;
    @Resource
    private ChangePasswordService changePasswordService;
    @Resource
    private UserJoinRoleService userJoinRoleService;

    @Override
    public LoginRes login(String loginId, String password) {
        AcUser user = acUserService.getUserByLoginId(loginId);
        if (user == null) {
            user = acUserService.getUserByPhoneNumber(loginId);
        }
        if (user == null) {
            user = acUserService.getUserByEmail(loginId);
        }
        Validator.assertNotNull(user, AccountErrCode.AC_USER_NOT_EXISTED);
        Validator.assertYes(user.getStatus(), AccountErrCode.AC_LOCK);
        verifyPassword(user, user.getPassword(), password);
        acUserService.updateByPrimaryKeySelective(user);
        return buildLoginRes(user);
    }

    private void verifyPassword(AcUser user, String passwordMd5, String password) {
        Validator.assertNotBlank(user.getPassword(), AccountErrCode.AC_PASSWORD_NOT_EXISTED);
        boolean verify = false;
        try {
            verify = MD5Util.verify(password, passwordMd5);
        } catch (Exception e) {
            Validator.assertException(AccountErrCode.AC_PASSWORD_LOGIN_FORMAT);
        }
        if (!verify) {
            int passwordMistakeMaxTime = frameCacheService.getParamInt(AccountParam.ACCOUNT_LOCK_MISTAKE_NUMBER);
            user.setPasswordErrorTimes(user.getPasswordErrorTimes() + 1);
            user.setPasswordErrorLastTime(new Date());
            if (user.getPasswordErrorTimes() >= passwordMistakeMaxTime) {
                user.setStatus(CommonConst.NO);
                acUserService.updateByPrimaryKeySelective(user);
                Validator.assertException(AccountErrCode.AC_PASSWORD_MAX_ERROR_TIMES);
            }
            acUserService.updateByPrimaryKeySelective(user);
            Validator.assertException(AccountErrCode.AC_PASSWORD_NOT_RIGHT, String.valueOf(passwordMistakeMaxTime - user.getPasswordErrorTimes()));
        }
    }

    private LoginRes buildLoginRes(AcUser user) {
        LoginRes res = ReflectionUtil.copy(user, LoginRes.class);
        fillToken(res, user);
        return res;
    }

    private void fillToken(LoginRes res, AcUser acUser) {
        String accessToken = buildAccessToken(res.getCustomerNumber(), res.getName());
        String refreshToken = buildRefreshToken(res.getCustomerNumber());
        res.setAccessToken(accessToken);
        res.setRefreshToken(refreshToken);
        buildPermitTree(acUser.getId(), accessToken);
        buildAccountInfo(res.getCustomerNumber(), accessToken);
    }

    private String buildAccessToken(String customerNumber, String nickName) {
        String accessToken = null;
        String clientType = getClientType();
        if (clientType.equals(ClientTypeConst.APP.name())) {
            accessToken = tokenService.buildAppAccessToken(customerNumber);
        } else if (clientType.equals(ClientTypeConst.WEB.name())) {
            accessToken = tokenService.buildWebAccessToken(customerNumber);
        } else if (clientType.equals(ClientTypeConst.WECHAT.name())) {
            accessToken = tokenService.buildWechatToken(customerNumber);
        }
        tokenService.putItem(accessToken, TokenItemConst.OPERATOR.name(), customerNumber);
        tokenService.putItem(accessToken, TokenItemConst.NICK_NAME.name(), nickName);
        return accessToken;
    }

    private String buildRefreshToken(String customerNumber) {
        String refreshToken = null;
        String clientType = getClientType();
        if (clientType.equals(ClientTypeConst.APP.name())) {
            refreshToken = tokenService.buildAppRefreshToken(customerNumber);
        } else if (clientType.equals(ClientTypeConst.WEB.name())) {
            refreshToken = tokenService.buildWebRefreshToken(customerNumber);
        }
        if (StringUtils.isNotBlank(refreshToken)) {
            tokenService.putItem(refreshToken, TokenItemConst.OPERATOR.name(), customerNumber);
        }
        return refreshToken;
    }

    private void buildPermitTree(String userId, String accessToken) {
        String clientType = getClientType();
        List<PermitRes> permitList = permitCoreService.getPermitListByUserId(userId, clientType);
        Validator.assertNotEmpty(permitList, AccountErrCode.AC_PERMIT_NOT_EXISTED);
        tokenService.putItem(accessToken, TokenItemConst.PERMIT_TREE.name(), permitList);
    }

    private void buildAccountInfo(String customerNumber, String accessToken) {
        UserWithRoleList userWithRoleList = userJoinRoleService.getUserWithRoleListByCustomerNumber(customerNumber);
        Map<Integer, AcRole> role = ReflectionUtil.reflectToMap(userWithRoleList.getRoleList(), "id", Integer.class);
        tokenService.putItem(accessToken, TokenItemConst.USER_INFO.name(), ReflectionUtil.copy(userWithRoleList,
                UserInfo.class));
        tokenService.putItem(accessToken, TokenItemConst.ROLE_MAP.name(), role);
    }

    private String getClientType() {
        String clientType = ClientTypeHolder.get();
        Validator.assertNotNull(clientType, ErrCodeSys.PA_DATA_NOT_EXIST, "客户端类型");
        return clientType;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public LoginRes loginOrReg(String phoneNumber) {
        AcUser user = acUserService.getUserByPhoneNumber(phoneNumber);
        if (user == null) {
            user = createUserByPhoneNumber(phoneNumber);
        } else {
            setUserLoginSuccessfully(user);
            acUserService.updateByPrimaryKeySelective(user);
        }
        return buildLoginRes(user);
    }

    private AcUser createUserByPhoneNumber(String phoneNumber) {
        return createUserWithName(phoneNumber, null);
    }

    private void setUserLoginSuccessfully(AcUser user) {
        user.setStatus(CommonConst.YES);
        user.setPasswordErrorTimes(0);
        user.setLastLoginTime(new Date());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public AcUser createUserWithName(String phoneNumber, String name) {
        return createUser(phoneNumber, null, name, phoneNumber, null, null);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public AcUser createUser(String loginId, String password, String name, String phoneNumber, String email, String avatar) {
        AcUser user = buildBaseUser();
        user.setLoginId(loginId);
        user.setPhone(phoneNumber);
        user.setPassword(MD5Util.generate(password));
        user.setName(name);
        user.setEmail(email);
        user.setAvatar(avatar);
        acUserService.insertSelective(user);
        bindDefaultRole(user);
        return user;
    }

    private AcUser buildBaseUser() {
        AcUser user = new AcUser();
        user.setStatus(CommonConst.YES);
        user.setPasswordErrorTimes(0);
        user.setValid(SqlConstant.VALID);
        return user;
    }

    private void bindDefaultRole(AcUser user) {
        String defaultRoleId = frameCacheService.getParamValueAvail(AccountParam.ACCOUNT_DEFAULT_ROLE_ID);
        bindRole(user.getId(), defaultRoleId);
    }

    @Override
    public void bindRole(String userId, String roleId) {
        acUserRoleService.replaceBind(userId, roleId);
    }

    @Override
    @Deprecated
    @Transactional(rollbackFor = Exception.class)
    public LoginRes loginOrRegByWechatMiniOpenId(String wechatOpenId) {
        AcUser user = acUserService.getUserByWechatId(wechatOpenId);
        if (user == null) {
            user = createUserByWechatUnionId(wechatOpenId, null);
        } else {
            setUserLoginSuccessfully(user);
            acUserService.updateByPrimaryKeySelective(user);
        }
        return buildLoginRes(user);
    }

    public AcUser createUserByWechatUnionId(String unionId, String nickName) {
        Validator.assertNotBlank(unionId, ErrCodeSys.PA_PARAM_NULL, "微信ID");
        AcUser user = buildBaseUser();
        user.setWechatId(unionId);
        if (StringUtils.isNotBlank(nickName)) {
            user.setName(nickName);
        }
        acUserService.insertSelective(user);
        bindDefaultRole(user);
        return user;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public LoginRes loginOrRegByWechatUnionId(String wechatUnionId, String nickName) {
        AcUser user = acUserService.getUserByWechatId(wechatUnionId);
        if (user == null) {
            user = createUserByWechatUnionId(wechatUnionId, nickName);
        } else {
            setUserLoginSuccessfully(user);
            acUserService.updateByPrimaryKeySelective(user);
        }
        return buildLoginRes(user);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public LoginRes loginOrRegByWechatPhoneNumber(String wechatId, String nickName, String phoneNumber) {
        AcUser user = bindWechatPhoneNumber(wechatId, nickName, phoneNumber);
        return buildLoginRes(user);
    }

    @Override
    public LoginRes refreshLogin(String refreshToken) {
        Validator.assertTrue(tokenService.isTokenExist(refreshToken), ErrCodeSys.SYS_SESSION_TIME_OUT);
        String customerNumber = tokenService.getItem(refreshToken, TokenItemConst.OPERATOR.name(), String.class);
        Validator.assertNotBlank(customerNumber, ErrCodeSys.PA_DATA_NOT_EXIST, "用户");
        AcUser user = acUserService.getUserByCustomerNumber(customerNumber);
        Validator.assertNotNull(user, AccountErrCode.AC_USER_NOT_EXISTED);
        Validator.assertYes(user.getStatus(), AccountErrCode.AC_LOCK);
        tokenService.removeToken(refreshToken);
        return buildLoginRes(user);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public LoginRes register(String userId, String password) {
        AcUser user = acUserService.getUserByLoginId(userId);
        Validator.assertNull(user, AccountErrCode.AC_USER_ALREADY_EXISTED);
        user = createUser(userId, password);
        return buildLoginRes(user);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public AcUser createUser(String userId, String password) {
        return createUser(userId, password, null, null, null, null);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public LoginRes register(String userId, String password, String email) {
        AcUser user = acUserService.getUserByLoginId(userId);
        Validator.assertNull(user, AccountErrCode.AC_USER_ALREADY_EXISTED);
        user = acUserService.getUserByEmail(email);
        Validator.assertNull(user, AccountErrCode.AC_USER_ALREADY_EXISTED);
        user = createUser(userId, password, email);
        return buildLoginRes(user);
    }

    private AcUser createUser(String userId, String password, String email) {
        return createUser(userId, password, null, null, email, null);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void changeAccountStatus(String userId, String status) {
        AcUser user = acUserService.getUserByLoginId(userId);
        Validator.assertNotNull(user, AccountErrCode.AC_USER_NOT_EXISTED);
        user.setStatus(status);
        acUserService.updateByPrimaryKeySelective(user);
    }

    @Override
    public void bindRoleByLoginId(String loginId, String roleId) {
        AcUser user = acUserService.getUserByLoginId(loginId);
        bindRole(user.getId(), roleId);
    }

    @Override
    public OpenPlatformRegResp registerOpenPlatform(String name) {
        String userId = AccountContext.getUserInfo().getUserId();
        AcUser user = acUserService.getUserById(userId);
        if (StringUtils.isBlank(user.getAppKey())) {
            String appKey = RandomUtil.getUUID();
            user.setAppKey(appKey);
        }
        String appSecret = RandomUtil.randomPassword();
        user.setAppSecret(MD5Util.MD5(appSecret).toUpperCase());
        user.setComment(name);
        acUserService.updateByPrimaryKeySelective(user);
        return buildOpenPlatformRegResp(user.getAppKey(), appSecret);
    }

    private OpenPlatformRegResp buildOpenPlatformRegResp(String appKey, String appSecret) {
        OpenPlatformRegResp res = new OpenPlatformRegResp();
        res.setAppKey(appKey);
        res.setAppSecret(appSecret);
        String authString = "basic " + Base64Util.encode(appKey + ":" + appSecret);
        res.setAuthString(authString);
        return res;
    }

    @Override
    public OpenPlatformRegResp getOpenPlatformAppSecret(String appKey) {
        AcUser user = acUserService.getUserByAppKey(appKey);
        String appSecret = RandomUtil.randomPassword();
        user.setAppSecret(MD5Util.MD5(appSecret).toUpperCase());
        acUserService.insertOrUpdate(user);
        return buildOpenPlatformRegResp(appKey, appSecret);
    }

    @Override
    public OpenPlatformAccessTokenResp getOpenPlatformAccessToken(String appKey, String appSecret) {
        AcUser user = acUserService.getUserByAppKey(appKey);
        Validator.assertNotNull(user, AccountErrCode.AC_USER_NOT_EXISTED);
        Validator.assertYes(user.getStatus(), AccountErrCode.AC_LOCK);
        verifyPassword(user, MD5Util.generate(user.getAppSecret()), MD5Util.generate(MD5Util.MD5(appSecret).toUpperCase()));
        String token = tokenService.buildOpenPlatformToken(appKey);
        tokenService.putItem(token, TokenItemConst.OPERATOR.name(), appKey);
        return buildOpenPlatformAccessTokenResp(appKey, token);
    }

    private OpenPlatformAccessTokenResp buildOpenPlatformAccessTokenResp(String appKey, String token) {
        OpenPlatformAccessTokenResp resp = new OpenPlatformAccessTokenResp();
        resp.setAccessToken(token);
        resp.setAppKey(appKey);
        return resp;
    }

    @Override
    public LoginRes ghostLogin(String userId) {
        AcUser user = acUserService.getUserByLoginId(userId);
        return buildLoginRes(user);
    }

    @Override
    public LoginRes loginByQrCode(QrCodeReq qrCodeReq) {
        Validator.assertNotBlank(qrCodeReq.getToken(), ErrCodeSys.PA_DATA_NOT_EXIST, "安全令牌");
        Validator.assertNotBlank(qrCodeReq.getUserId(), ErrCodeSys.PA_DATA_NOT_EXIST, "用户");
        AcUser user = acUserService.getUserById(qrCodeReq.getUserId());
        Validator.assertNotNull(user, AccountErrCode.AC_USER_NOT_EXISTED);
        Validator.assertYes(user.getStatus(), AccountErrCode.AC_LOCK);
        return buildLoginRes(user);
    }

    @Override
    public void emailRegister(String id, String email) {
        AcUser user = acUserService.getUserByLoginId(id);
        Validator.assertNull(user, ErrCodeSys.PA_DATA_HAS_EXIST, "用户名");
        user = acUserService.getUserByEmail(email);
        Validator.assertNull(user, ErrCodeSys.PA_DATA_HAS_EXIST, "邮箱");
        changePasswordService.sendChangePasswordEmail(id, email);
    }

    private AcUser bindWechatPhoneNumber(String unionId, String nickName, String phoneNumber) {
        Validator.assertNotBlank(phoneNumber, ErrCodeSys.PA_PARAM_NULL, "微信账号绑定手机号");
        AcUser user;
        if (StringUtils.isNotBlank(unionId)) {
            user = acUserService.getUserByWechatId(unionId);
            if (user == null) {
                user = createUserByWechatUnionId(unionId, nickName);
            }
        } else {
            user = acUserService.getUserByPhoneNumber(phoneNumber);
            if (user == null) {
                user = createUserByPhoneNumber(phoneNumber);
            }
        }
        user.setName(nickName);
        user.setPhone(phoneNumber);
        try {
            acUserService.updateByPrimaryKeySelective(user);
        } catch (DuplicateKeyException e) {
            mergeWechatPhoneNumber(unionId, nickName, phoneNumber);
        }
        return user;

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void mergeWechatPhoneNumber(String unionId, String nickName, String phoneNumber) {
        AcUser user = acUserService.getUserByPhoneNumber(phoneNumber);
        Validator.assertBlank(user.getWechatId(), ErrCodeSys.SYS_ERR_MSG, "手机号账户已绑定微信");
        user.setWechatId(unionId);
        if (StringUtils.isBlank(user.getName())) {
            user.setName(nickName);
        }
        acUserService.deleteByUserId(unionId);
        acUserService.updateByPrimaryKeySelective(user);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public LoginRes ssoLoginOrReg(SsoLoginReq ssoLoginReq) {
        AcUser partner = acUserService.getUserByAppKey(ssoLoginReq.getAppId());
        Validator.assertNotNull(partner, ErrCodeSys.PA_DATA_NOT_EXIST, "appId用户");
        DockingSignUtil.validateSign(ssoLoginReq.getTimeStamp(), ssoLoginReq.getNonce(), ssoLoginReq.getSignature(),
                partner.getAppSecret());
        AcUser user = acUserService.getUserByLoginId(ssoLoginReq.getLoginId());
        if (user == null) {
            user = createUser(ssoLoginReq.getLoginId(), ssoLoginReq.getLoginId());
        }
        if (!StringUtils.equals(user.getName(), ssoLoginReq.getUserName())) {
            user.setName(ssoLoginReq.getUserName());
        }
        setUserLoginSuccessfully(user);
        acUserService.updateByPrimaryKeySelective(user);
        return buildLoginRes(user);
    }
}
